package trieMatch.keywordMatcher;

import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

import trieMatch.Constants;
import trieMatch.Interfaces.ListProcessorIF;
import trieMatch.consoleApps.MultiQuery;
import trieMatch.util.TOD;

/**
 * Threaded implementation of <code>ListProcessorIF<String></code> using
 * <code>java.util.concurrent.ExecutorService</code>.<br>
 * 
 * The executor service runs efficiently and provides several more advantages of significance. The methodology improves
 * reliability through a reusable pattern. The API describes many of the characteristics of threaded processing. The
 * implementation handles many runtime intricacies, including management of storage and thread resources.
 * 
 * @author mlrus
 * 
 */

public class ProcessQueries implements ListProcessorIF<String> {
	Queue<List<String>> workQueue;
	final ExecutorService execSvc;
	ArrayList<Callable<RequestResult>> executorResultList;
	PrintStream outputStream;

	public ProcessQueries(final int numThreads) {
		this(numThreads, null);
	}

	public ProcessQueries(final int numThreads, final PrintStream outputStream) {
		execSvc = Executors.newFixedThreadPool(numThreads);
		executorResultList = prepareResultContainers(numThreads);
		this.outputStream = outputStream;
	}

	public void setPrintStream(final PrintStream outputStream) {
		this.outputStream = outputStream;
	}

	public void processQuerylist(final List<String> queryList) throws Exception {
		processQuerylist(queryList, MultiQuery._chunkSize);
	}

	public void processQuerylist(final List<String> queryList, final int chunkSize) throws Exception {
		long totalTime = 0;
		int totalQueries = 0, currQueries = 0;
		for (int rep = 0; rep < MultiQuery.getNumRep(); rep++) {
			workQueue = mkWorkQueue(queryList, chunkSize);
			long mtime = System.currentTimeMillis();
			final List<Future<RequestResult>> resultList = execSvc.invokeAll(executorResultList);
			totalTime += (mtime = System.currentTimeMillis() - mtime);
			totalQueries += (currQueries = sumResults(resultList));
			System.out.printf("Repetition " + rep + " completed. %s: queries processed=%d, search time=%d msec, %8f msec/mquery\n",
					TOD.now(), currQueries, mtime, (mtime / (1D * queryList.size())));
		}
		MultiQuery.showStats(totalTime, totalQueries);
		execSvc.shutdown();
		System.out.println(TOD.now() + ": executor service terminating");
		execSvc.awaitTermination(60, TimeUnit.SECONDS);
		System.out.println(TOD.now() + ": executor service terminated");
	}

	ArrayList<Callable<RequestResult>> prepareResultContainers(final int numContainers) {
		executorResultList = new ArrayList<Callable<RequestResult>>();
		for (int i = 0; i < numContainers; i++) {
			executorResultList.add(new Req());
		}
		return executorResultList;
	}

	int sumResults(final List<Future<RequestResult>> resultList) throws InterruptedException, ExecutionException {
		int numQueries = 0;
		for (final Future<RequestResult> result : resultList) {
			final RequestResult rr = result.get();
			numQueries += rr.nQueries;
		}
		return numQueries;
	}

	public Queue<List<String>> mkWorkQueue(final List<String> allQueries, final int chunkSize) {
		int qIndex = 0;
		final int capacity = 1 + allQueries.size() / chunkSize;
		final ArrayBlockingQueue<List<String>> queryListQueue = new ArrayBlockingQueue<List<String>>(capacity, false);
		for (qIndex = 0; qIndex < allQueries.size() - chunkSize; qIndex += chunkSize) {
			queryListQueue.add(allQueries.subList(qIndex, qIndex + chunkSize));
		}
		if (qIndex < allQueries.size()) {
			queryListQueue.add(allQueries.subList(qIndex, allQueries.size()));
		}
		return queryListQueue;
	}

	class Req implements Callable<RequestResult> {
		TrieMatcher trieMatcher;
		List<String> queries;
		RequestResult requestResult;
		ResultFormatter resultFormatter = new ResultFormatter();

		public Req() {
			this(new ArrayList<String>());
		}

		public Req(final List<String> queries) {
			this(queries, new RequestResult());
		}

		public Req(final List<String> queries, final RequestResult requestResult) {
			this(queries, requestResult, MultiQuery.getSearchFlavor().select(MultiQuery.kmPrimary.tiers));
		}

		public Req(final List<String> queries, final RequestResult requestResult, final TrieMatcher trieMatcher) {
			this.queries = queries;
			this.requestResult = requestResult;
			this.trieMatcher = trieMatcher;
		}

		public void run() {
			requestResult.nQueries = 0;
			requestResult.tStart = System.currentTimeMillis();
			final Sequencer sq = new Sequencer(); // thread's own sequencer
			while ((this.queries = workQueue.poll()) != null) {
				final StringBuffer sb = new StringBuffer(4096);
				for (final String q : queries) {
					// INLINED: final List<String> rans = MultiQuery.kmPrimary.processQuery(this.trieMatcher, q);
					final List<KMDefinition> ans = trieMatcher.findMatch(sq, q);
					final List<String> rans = resultFormatter.formatResults(trieMatcher.reduce(ans));
					if (outputStream != null) {
						sb.append(String.format("#results %-6d: %s%n", rans.size(), q));
						for (final String st : rans) {
							sb.append(String.format("%-60s <== %s%n", st, q));
							if (!Constants.SHOWALL) {
								break;
							}
						}
					}
				}
				if (outputStream != null) {
					outputStream.print(sb.toString());
				}
				this.requestResult.tEnd = System.currentTimeMillis();
				this.requestResult.tTotal += (requestResult.tEnd - requestResult.tStart);
				this.requestResult.nQueries += this.queries.size();
			}
		}

		public RequestResult call() throws Exception {
			run();
			return requestResult;
		}
	}

	class RequestResult {
		int nQueries;
		long tStart;
		long tEnd;
		long tTotal;
		Object result;

		RequestResult() {
			nQueries = 0;
			tStart = System.currentTimeMillis();
			tEnd = -1;
			result = null;
		}

		@Override
		public String toString() {
			return String.format("nQueries=%d timeTotal=%d msec/query=%f", nQueries, tTotal, (1D * tTotal) / (1D * nQueries));
		}
	}
}